package com.iwechat.api.mina;

import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.PostConstruct;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import com.wctm.adapter.mina.entity.Connect;
import com.wctm.adapter.mina.entity.PDU;


public class NioClient {
    public static final String          SESSION_POOL_BEAN_NAME = "sessionPool";

    private static final Logger         logger                 = LogManager.getLogger();
    private String                      clientId;
    private String                      host;
    private int                         maxConn;
    private int                         port;
    private int                         idleTime;
    private int                         connTimeOut;
    private int                         readTimeOut;
    private NioSocketConnector          connector;
    private NioClientHandlerAdapter     ioHandler;
    private DefaultIoFilterChainBuilder filterChain;
    private ExecutorService             poolService;
    private List<IoSession>             sessionPool;

    @PostConstruct
    public void initc() {
        sessionPool = Collections.synchronizedList(new LinkedList<IoSession>());

        logger.info("### " + this + " init succeed!");
    }

    @Bean(name = SESSION_POOL_BEAN_NAME)
    @Scope("singleton")
    public List<IoSession> getSessionPool() {
        return sessionPool;
    }

    /**
     * 初始化 客户端
     */
    public void init() {
        logger.info("{}, startting config...", this);

        connector.setConnectTimeoutMillis(connTimeOut * 1000);
        connector.setFilterChainBuilder(filterChain);

        poolService = Executors.newCachedThreadPool();
        filterChain.addLast("threadPool", new ExecutorFilter(poolService));

        connector.setHandler(ioHandler);

        SocketSessionConfig config = connector.getSessionConfig();
        config.setBothIdleTime(idleTime);
        config.setReuseAddress(true);
        config.setMinReadBufferSize(PDU.HEAD_LEN);

        logger.info("{}, config finish.", this);
    }

    /**
     * 获取一个会话。<br>
     * 逻辑：sessionPool.size() < maxConn 时创建新会话。 否则返回一个较为空闲的会话。
     *
     * 
     * @return may be null
     */
    public IoSession getSession() {
        IoSession session = null;
        if (sessionPool.size() < maxConn) {
            // 创建新会话
            InetSocketAddress addr = new InetSocketAddress(host, port);
            ConnectFuture cfu = connector.connect(addr);
            cfu.awaitUninterruptibly();
            session = cfu.getSession();
            // 登录
            Connect connect = new Connect(clientId);
            WriteFuture wfu = session.write(connect);
            wfu.awaitUninterruptibly();
            synchronized (session) {
                try {
                    session.wait();
                    sessionPool.add(session);
                } catch (InterruptedException e) {
                }
            }
        } else {
            // 获取一个较为空闲的会话
            long last = Long.MAX_VALUE, curr = 0;
            for (IoSession s : sessionPool) {
                curr = s.getLastIoTime();
                if (curr < last) {
                    session = s;
                    last = curr;
                }
            }
        }

        if (session != null && session.isActive()) {
            return session;
        } else {
            if (session != null) {
                sessionPool.remove(session);
                session.closeNow();
            }
            return null;
        }
    }

    /**
     * 销毁客户端
     *
     * 
     */
    public void destroy() {
        this.connector.dispose(false);
        this.poolService.shutdown();
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public int getIdleTime() {
        return idleTime;
    }

    public void setIdleTime(int idleTime) {
        this.idleTime = idleTime;
    }

    public int getConnTimeOut() {
        return connTimeOut;
    }

    public void setConnTimeOut(int connTimeOut) {
        this.connTimeOut = connTimeOut;
    }

    public int getReadTimeOut() {
        return readTimeOut;
    }

    public void setReadTimeOut(int readTimeOut) {
        this.readTimeOut = readTimeOut;
    }

    public NioSocketConnector getConnector() {
        return connector;
    }

    public void setConnector(NioSocketConnector connector) {
        this.connector = connector;
    }

    public NioClientHandlerAdapter getIoHandler() {
        return ioHandler;
    }

    public void setIoHandler(NioClientHandlerAdapter ioHandler) {
        this.ioHandler = ioHandler;
    }

    public DefaultIoFilterChainBuilder getFilterChain() {
        return filterChain;
    }

    public void setFilterChain(DefaultIoFilterChainBuilder filterChain) {
        this.filterChain = filterChain;
    }

    public int getMaxConn() {
        return maxConn;
    }

    public void setMaxConn(int maxConn) {
        this.maxConn = maxConn;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    /**
     * @return the port
     */
    public int getPort() {
        return port;
    }

    /**
     * @param port
     *            the port to set
     */
    public void setPort(int port) {
        this.port = port;
    }
}
